﻿using Autofac.Core;
using PlutoStudio.Dependency;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Castle.Core;
using System.Linq;
using Autofac;
using Castle.DynamicProxy.Internal;
using System.Globalization;
using Autofac.Features.AttributeFilters;
using Castle.DynamicProxy;

namespace PlutoStudio.Aspects
{
    /// <summary>
    /// This class is used to register interceptor for needed classes for Unit Of Work mechanism.
    /// </summary>
    internal class InterceptorInstaller:IocInstaller
    {

        private const string InterceptorsPropertyName = "Autofac.Extras.DynamicProxy.RegistrationExtensions.InterceptorsPropertyName";

        private const string AttributeInterceptorsPropertyName = "Autofac.Extras.DynamicProxy.RegistrationExtensions.AttributeInterceptorsPropertyName";

        private static readonly IEnumerable<Service> EmptyServices = Enumerable.Empty<Service>();

        private static readonly ProxyGenerator ProxyGenerator = new ProxyGenerator();

        private static readonly IDictionary<Type, TypeInterceptorMap> interceptorMaps = new Dictionary<Type,TypeInterceptorMap>();

        private static readonly List<IConventionaInterceptorRegistrar> _registrars = new List<IConventionaInterceptorRegistrar>();

        public static List<IConventionaInterceptorRegistrar> Registrars => _registrars;
        protected override void Load(ContainerBuilder builder)
        {
            var context = new ConventionaInterceptorContext(interceptorMaps);
            _registrars.AsReadOnly().ForEach(r => r.Register(context));
            base.Load(builder);
        }

        protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration)
        {
            var componentType = registration.Activator.LimitType;
            if (IsInterceptorType(componentType) || AnyMethodHasInterceptor(componentType)||IsConventionalInterceptorType(componentType))
            {
                HandleTypesWithInterceptorAttribute(componentType, registration);
                HandleConventionalInterceptorTypes(componentType, registration);
                HandleTypesWithInterceptor(componentType, registration);

            }
        }

        private static void HandleConventionalInterceptorTypes(Type componentType, IComponentRegistration registration)
        {
            if (IsConventionalInterceptorType(componentType))
            {
                var type = interceptorMaps.Keys.Where(t => t.IsAssignableFrom(componentType)).FirstOrDefault();
                var interceptorTypes =interceptorMaps[type].InterceptorTypes;
                AddInterceptorServicesToMetadata(registration, InterceptorsPropertyName, interceptorTypes.Select(t => new TypedService(t)).ToArray());
            }

        }

        private static bool IsConventionalInterceptorType(Type componentType)
        {
            return interceptorMaps.Keys.Any(t=>t.IsAssignableFrom(componentType));
        }

        private static void HandleTypesWithInterceptor(Type componentType, IComponentRegistration registration)
        {
            registration.Activating += (o, e) =>
            {
                if (!EnsureInterfaceInterceptionApplies(e.Component))
                {
                    return;
                }

                var proxiedInterfaces = e.Instance
                    .GetType()
                    .GetInterfaces()
                    .Where(i =>
                    {
                        var ti = i.GetTypeInfo();
                        return ti.IsVisible || ti.Assembly.IsInternalToDynamicProxy();
                    })
                    .ToArray();

                if (!proxiedInterfaces.Any())
                {
                    return;
                }

                var theInterface = proxiedInterfaces.First();
                var interfaces = proxiedInterfaces.Skip(1).ToArray();

                var interceptors = GetInterceptorServices(e.Component, e.Instance.GetType())
                    .Select(s => e.Context.ResolveService(s))
                    .Cast<IInterceptor>()
                    .ToArray();

                e.Instance = ProxyGenerator.CreateInterfaceProxyWithTarget(theInterface, interfaces, e.Instance, interceptors);

            };

        }

        private static void HandleTypesWithInterceptorAttribute(Type componentType, IComponentRegistration registration)
        {
            if (IsInterceptorType(componentType) || AnyMethodHasInterceptor(componentType))
            {
                var interceptorTypes = GetInterceptorTypes(componentType);
                AddInterceptorServicesToMetadata(registration, InterceptorsPropertyName, interceptorTypes.Select(t => new TypedService(t)).ToArray());
            }
        }

        private static IEnumerable<Type> GetInterceptorTypes(Type componentType)
        {
            return componentType.GetAttributes<InterceptorAttribute>(true).Select(a => a.InterceptorType)
                .Concat(componentType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Select(m => m.GetAttributes<InterceptorAttribute>(true).Select(a => a.InterceptorType)).Expand()).Distinct();
        }

        private static bool IsInterceptorType(Type implementationType)
        {
            return InterceptorHelper.HasInterceptorAttribute(implementationType);
        }

        private static bool AnyMethodHasInterceptor(Type implementationType)
        {
            return implementationType
                .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                .Any(InterceptorHelper.HasInterceptorAttribute);
        }

        private static void AddInterceptorServicesToMetadata(IComponentRegistration componentRegistration, string metadataKey, params Service[] interceptorServices)
        {
            object existing;
            if (componentRegistration.Metadata.TryGetValue(metadataKey, out existing))
            {
                componentRegistration.Metadata[metadataKey] =
                    ((IEnumerable<Service>)existing).Concat(interceptorServices).Distinct();
            }
            else
            {
                componentRegistration.Metadata.Add(metadataKey, interceptorServices);
            }
        }

        private static bool EnsureInterfaceInterceptionApplies(IComponentRegistration componentRegistration)
        {
            if (componentRegistration.Services
                .OfType<IServiceWithType>()
                .Select(s => s.ServiceType.GetTypeInfo())
                .Any(s => s.IsInterface && (s.Assembly.IsInternalToDynamicProxy() || s.IsVisible)))
            {
                return true;
            }
            return false;
        }

        private static IEnumerable<Service> GetInterceptorServices(IComponentRegistration registration, Type implType)
        {
            if (registration == null)
            {
                throw new ArgumentNullException(nameof(registration));
            }

            if (implType == null)
            {
                throw new ArgumentNullException(nameof(implType));
            }

            var result = EmptyServices;

            object services;
            if (registration.Metadata.TryGetValue(InterceptorsPropertyName, out services))
            {
                result = result.Concat((IEnumerable<Service>)services);
            }
            return result;
        }
    }
}
